微服务SpringCloud负载均衡详解 |
您所在的位置:网站首页 › 负载均衡 ctri › 微服务SpringCloud负载均衡详解 |
在微服务Spring Cloud快速入门中,我们学习了几个知识点:注册中心、服务注册、服务发现、Feign Http调用。 那么在本文中,我们将开始详细展开,负载均衡。 文章目录 1.什么是负载均衡2.注册多个服务2.1向注册中心注册多个服务2.2通过服务ID找到服务2.2.1.DiscoveryClient 解读2.2.2.使用DiscoveryClient获取服务实例2.2.3.从服务实例中获取服务信息,发起Http请求2.2.4.多个服务实例,如何负载均衡2.2.5.简单验证负载均衡 3.分析@LoadBalanced实现负载均衡源码解析3.1.RestTemplate源码解析3.2.LoadBalancerInterceptor源码解析3.3.RibbonLoadBalancerClient源码解析 4.负载均衡代码验证演示5.负载均衡配置6.总结 1.什么是负载均衡首先我们来看看维基百科对负载均衡的说明: 负载平衡(Load balancing)是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。 使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。 主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题。 举个例子来解释下负载均衡 下图中,一群人在银行排队办理业务,假设只有一个服务窗口,那么一个服务窗口来处理所有人员业务办理,人少的时候,肯定是能够办理完的,如果人特别多的时候恩?一个服务窗口肯定是没有办法处理完这么多业务办理的。于是就出现如下图这样,一大堆人排队。 在微服务Spring Cloud快速入门中,如果我们的商品服务一个不够用的话,根据负载均衡的理论,那我们就可以多注册几个服务。防止一个服务无法承载高并发时的情况。、 启动注册中心、商品服务,订单服务,登录注册中心,查看我们当前的服务 以同样的项目代码,设定端口为8091,其余配置均和之前的商品服务保持一致,然后启动商品服务。 server: port: 8091 #服务端口 spring: application: name: hutao-microservice-item #指定服务名 eureka: client: registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true fetchRegistry: true #是否从Eureka中获取注册信息,默认为true serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址 defaultZone: http://127.0.0.1:9090/eureka/ instance: prefer-ip-address: true #将自己的ip地址注册到Eureka服务中点击如下所示,可以看到我们启动了1个注册中心,1个订单服务,2个商品服务 在微服务Spring Cloud快速入门中我们定义了如下Feign接口,在这个接口上面,我们添加了一个注解@FeignClient,指定了服务ID:hutao-microservice-item。这个Feign接口会给我发起Http调用。当然在这里我们可能无法看到负载均衡的效果,因此我们需要稍微深入下底层代码。 @FeignClient(value = "hutao-microservice-item") @RequestMapping("/itemservice") public interface FeignOrderService { /** * @Description:使用声明式HTTP客户端发起请求:根据ID查询订单 * @author hutao * @mail [email protected] * @date 2020年8月30日 */ @GetMapping(value = "item/{itemId}") Items queryItem(@PathVariable("itemId")String itemId); }大家应该还记得,我们的商品服务和订单服务,注册到注册中心的时候,我们在启动类上面添加了一个注解:@EnableDiscoveryClient。 现在我们来看下DiscoveryClient这个接口 2.2.1.DiscoveryClient 解读DiscoveryClient接口源码如下,他是用来发现服务的,比如发现Netflix服务,其中有个方法List getInstances(String serviceId)是根据服务ID获取服务实例集合。 /** * Represents read operations commonly available to discovery services such as Netflix * Eureka or consul.io. * * @author Spencer Gibb * @author Olga Maciaszek-Sharma */ public interface DiscoveryClient extends Ordered { /** * Default order of the discovery client. */ int DEFAULT_ORDER = 0; /** * A human-readable description of the implementation, used in HealthIndicator. * @return The description. */ String description(); /** * Gets all ServiceInstances associated with a particular serviceId. * @param serviceId The serviceId to query. * @return A List of ServiceInstance. */ List getInstances(String serviceId); /** * @return All known service IDs. */ List getServices(); /** * Default implementation for getting order of discovery clients. * @return order */ @Override default int getOrder() { return DEFAULT_ORDER; } }我们可以看到DiscoveryClient 有4个实现,当然这里我们用的肯定是Eureka。 现在我们来使用下DiscoveryClient 这个接口 首先把这个接口依赖注入到我们的Controller中,我们看下能获取到什么 @Autowired private DiscoveryClient discoveryClient; @GetMapping(value = "order/{orderId}") public Order queryOrderById(@PathVariable("orderId") String orderId) { String serviceId = "hutao-microservice-item"; List instances = discoveryClient.getInstances(serviceId); System.out.println(instances); return null; }通过调试代码,我们发现如我们之前所说,我们可以通过服务ID:hutao-microservice-item,找到两个服务实例。 那么这个时候,我们怎么去发起请求恩?很简单,就是从服务实例中,获取到服务信息后,将接口的请求地址拼接出来。然后使用restTemplate发起Http请求 @Autowired private RestTemplate restTemplate; @GetMapping(value = "order/{orderId}") public Order queryOrderById(@PathVariable("orderId") String orderId) { String serviceId = "hutao-microservice-item"; List instances = discoveryClient.getInstances(serviceId); ServiceInstance serviceInstance = instances.get(0); String url = serviceInstance.getHost() + ":" + serviceInstance.getPort(); Items items = restTemplate.getForObject("http://" + url + "/itemservice/item/1" , Items.class); System.out.println(items); return null; }可以看到我们的请求是能正常访问的。当然也有问题存在,那就是我们拿到的是多个服务,程序怎么知道我要调用的是哪一个服务恩? 上述案例中,我们获取到的实例是两个,那么每次调用的时候,我们怎么来确定,我要调用的是哪一个服务?,因为这时候我们拿到的两个服务,也就是两个不同的IP地址,这时候就需要一个负载均衡器来帮我们选择一个IP进行访问。 在Spring Cloud中,netfix提供一个负载均衡器Ribbon,该负载均衡器是声明式的,其用法如下所示,在我们注入到Spring容器中的RestTemplate添加注解@LoadBalanced,这时候我们的RestTemplate就具备了负载均衡的功能。 注意:RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,可以替换实现的。如下源码所示,RestTemplate提供了三个构造方法。 在上面的一个案例中,我们通过从服务实例中,获取到服务,然后在从服务中心获取具体的IP地址信息,发起请求。 String url = serviceInstance.getHost() + ":" + serviceInstance.getPort(); Items items = restTemplate.getForObject("http://" + url + "/itemservice/item/1" ,但是,现在我们不需要这么做了,因为我们对restTemplate声明了是需要负载均衡的,因此我们发起请求的时候,就不需要指定IP地址了,我们可以用服务ID来代替IP地址,然后由restTemplate来帮我选择需要调用的IP。因此上述代码会被简化为如下所示,被注释掉的代码就是被优化的代码。 @GetMapping(value = "order/{orderId}") public Order queryOrderById(@PathVariable("orderId") String orderId) { String serviceId = "hutao-microservice-item"; //List instances = discoveryClient.getInstances(serviceId); //ServiceInstance serviceInstance = instances.get(0); //String url = serviceInstance.getHost() + ":" + serviceInstance.getPort(); //Items items = restTemplate.getForObject("http://" + url + "/itemservice/item/1" , Items.class); Items items = restTemplate.getForObject("http://" + serviceId + "/itemservice/item/1" , Items.class); System.out.println(items); return null; }重启服务后,商品服务仍然可用,那么这时候怎么来验证我们的负载均衡成功了? 2.2.5.简单验证负载均衡其实最简单的验证方式就是,在商品服务中,添加日志,看看哪个服务记录了日志或者debug调试,看走哪一个服务的代码。即可验证我们的负载均衡是否成功。 当然这里我们就不做上述方式的演示,来做一点高难度的代码分析。 3.分析@LoadBalanced实现负载均衡源码解析 3.1.RestTemplate源码解析1、首先看org.springframework.web.client.RestTemplate类。 当我们执行如下代码时 restTemplate.getForObject("http://" + serviceId + "/itemservice/item/1" , Items.class);最终会执行到如下方法 doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor)
逐步深入代码,我们找到了如下的拦截器。LoadBalancerInterceptor org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor 继续深入代码,我们找到了RibbonLoadBalancerClient这个类 org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient 通过查看代码,我们发现了getLoadBalancer(String)这个方法通过serviceId找到了两个服务实例, 也就是说getServer这个方法给我们实现了负载均衡。看源代码知道,如果为设置负载均衡参数,就使用default。否则就根据配置的参数选择服务。 protected Server getServer(ILoadBalancer loadBalancer, Object hint) { if (loadBalancer == null) { return null; } // Use 'default' on a null hint, or just pass it on? return loadBalancer.chooseServer(hint != null ? hint : "default"); }
通过上述源码分析,我们发现如果我们开启了负载均衡,但是没有配置负载均衡参数,则会采用默认的配置,也就是轮询算法来实现负载均衡。 通过上述的debug阅读,我们参照getServer这个方法来写代码测试下负载均衡 @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping(value = "order/{orderId}") public Order queryOrderById(@PathVariable("orderId") String orderId) { String serviceId = "hutao-microservice-item"; for(int i = 0 ; i |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |